﻿#nullable enable

using System;
using System.Diagnostics.CodeAnalysis;
using Android.App;
using Android.Views;

namespace Xamarin.Android.Design
{
	public delegate Java.Lang.Object? OnLayoutItemNotFoundHandler (int resourceId, Type expectedViewType);

	abstract class LayoutBinding
	{
		const DynamicallyAccessedMemberTypes Constructors = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors;

		Activity? boundActivity;
		View? boundView;
		OnLayoutItemNotFoundHandler? onLayoutItemNotFound;

		protected LayoutBinding (Activity activity, OnLayoutItemNotFoundHandler? onLayoutItemNotFound = null)
		{
			boundActivity = activity ?? throw new ArgumentNullException (nameof (activity));
			this.onLayoutItemNotFound = onLayoutItemNotFound;
		}

		protected LayoutBinding (View view, OnLayoutItemNotFoundHandler? onLayoutItemNotFound = null)
		{
			boundView = view ?? throw new ArgumentNullException (nameof (view));
			this.onLayoutItemNotFound = onLayoutItemNotFound;
		}

		protected T FindView <
				[DynamicallyAccessedMembers (Constructors)]
				T
		> (
				int resourceId,
				ref T cachedField)
			where T: View
		{
			if (cachedField != null)
				return cachedField;

			T? ret = null;
			if (boundActivity != null)
				ret = boundActivity.FindViewById <T> (resourceId);
			else if (boundView != null)
				ret = boundView.FindViewById <T> (resourceId);

			if (ret == null && onLayoutItemNotFound != null)
				ret = (T?)onLayoutItemNotFound (resourceId, typeof (T));

			if (ret == null)
				throw new global::System.InvalidOperationException ($"View not found (Resource ID: {resourceId})");

			cachedField = ret;
			return ret;
		}

		Activity EnsureActivity ()
		{
			if (boundActivity != null)
				return boundActivity;

			var ret = boundView?.Context as Activity;
			if (ret != null)
				return ret;

			throw new InvalidOperationException ("Finding fragments is supported only for Activity instances");
		}

		T __FindFragment<
				[DynamicallyAccessedMembers (Constructors)]
				T
		> (
				int resourceId,
				Func<Activity, T?> finder,
				ref T? cachedField)
			where T: Java.Lang.Object
		{
			if (cachedField != null)
				return cachedField;

			var ret = finder (EnsureActivity ());
			if (ret == null && onLayoutItemNotFound != null)
				ret = (T?)onLayoutItemNotFound (resourceId, typeof (T));

			if (ret == null)
				throw new InvalidOperationException ($"Fragment not found (ID: {resourceId}; Type: {typeof (T)})");

			cachedField = ret;
			return ret;
		}
#if __ANDROID_11__
#pragma warning disable CA1422
		protected T FindFragment<
				[DynamicallyAccessedMembers (Constructors)]
				T
		> (
				int resourceId,
				global::Android.App.Fragment? __ignoreMe,
				ref T? cachedField
		)
			where T: global::Android.App.Fragment
		{
			return __FindFragment<T> (resourceId, (activity) => activity.FragmentManager?.FindFragmentById<T> (resourceId), ref cachedField);
		}
#pragma warning restore CA1422
#endif  // __ANDROID_11__

#if __HAVE_ANDROIDX__
		protected T FindFragment<T> (int resourceId, global::AndroidX.Fragment.App.Fragment? __ignoreMe, ref T? cachedField)
			where T : global::AndroidX.Fragment.App.Fragment
		{
			return __FindFragment(resourceId, (activity) => {
				if (activity is AndroidX.Fragment.App.FragmentActivity activity_) {
					return global::Android.Runtime.Extensions.JavaCast<T>(activity_.SupportFragmentManager.FindFragmentById(resourceId));
				}
				throw new InvalidOperationException ($"When using AndroidX, your activity needs to derive from a subclass of {nameof(AndroidX.Fragment.App.FragmentActivity)}.");
			}, ref cachedField);
		}
#endif // __HAVE_ANDROIDX__
	}
}
